﻿//*****************************************************************************
//
//  Copyright 2008 IT Cloud Company
//
//  This software is available under the Microsoft Public License.  Please
//  read the license agreement at http://opensource.org/licenses/ms-pl.html
//  before using the software.
//
//  Authors:
//    Don Song (don@itcloud.com)
//
//*****************************************************************************

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Xml;
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;

namespace HeadLinx.AD.Common
{
    public class HeadLinxUser
    {
        private bool _selectedUser = false;
        private HeadLinxUserDictionary _directReports = new HeadLinxUserDictionary();
        private HeadLinxPropertyMap _propMap = new HeadLinxPropertyMap();

        public HeadLinxUser()
        {
        }

        public override string ToString()
        {
            return String.Format("User: {0}", this.DistinguishedName);
        }

        //public static HeadLinxUser CreateFromAD(string userDN)
        //{
        //    HeadLinxUser user = new HeadLinxUser();

        //    using (DirectoryEntry entry = new DirectoryEntry("LDAP://" + userDN))
        //    {
        //        foreach (string propName in ADUserPropertyNames.All)
        //        {
        //            if (entry.Properties.Contains(propName))
        //            {
        //                HeadLinxPropertyValueCollection hlPropValues = new HeadLinxPropertyValueCollection();

        //                foreach (object adPropValue in entry.Properties[propName])
        //                {
        //                    hlPropValues.Add(adPropValue);
        //                }

        //                user._propMap[propName] = hlPropValues;
        //            }
        //        }
        //    }

        //    return user;
        //}

        public static HeadLinxUser CreateFromADSearchResult(SearchResult result)
        {
            HeadLinxUser user = new HeadLinxUser();

            foreach (string propName in ADUserPropertyNames.All)
            {
                if (result.Properties.Contains(propName))
                {
                    HeadLinxPropertyValueCollection hlPropValues = new HeadLinxPropertyValueCollection();

                    foreach (object adPropValue in result.Properties[propName])
                    {
                        hlPropValues.Add(adPropValue);
                    }

                    user._propMap[propName] = hlPropValues;
                }
            }

            return user;
        }

        private static string DumpSingle(HeadLinxUser user, int level)
        {
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < level; ++i)
            {
                sb.Append("\t");
            }

            if (user.SelectedUser)
            {
                sb.Append("*** ");
            }

            sb.AppendLine(user.DistinguishedName);

            return sb.ToString();
        }

        public static HeadLinxUser LoadFromXml(Stream inputStream)
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(inputStream);

            HeadLinxUser person = new HeadLinxUser();
            XmlElement personElem = (XmlElement)doc.DocumentElement.SelectSingleNode("Object");
            LoadXmlProps(personElem, person);

            return person;
        }

        private static void LoadXmlProps(XmlElement personElem, HeadLinxUser person)
        {
            LoadRequiredProperties(personElem, person);

            foreach (XmlElement childElem in personElem.SelectNodes("Object"))
            {
                HeadLinxUser childPerson = new HeadLinxUser();
                LoadXmlProps(childElem, childPerson); // recurse
                person.DirectReports[childPerson.DistinguishedName] = childPerson;
            }
        }

        public void SaveToXml(Stream outputStream)
        {
            XmlDocument doc = new XmlDocument();

            doc.LoadXml("<?xml version=\"1.0\"?><Objects/>");

            XmlElement personElem = doc.CreateElement("Object");
            SaveXmlProps(doc, personElem, this);
            doc.DocumentElement.AppendChild(personElem);

            doc.Save(outputStream);
        }

        private static void AddChildPropertyElement(XmlElement parentElem, string propName, string valueMimeType, string value)
        {
            XmlElement propElem = parentElem.OwnerDocument.CreateElement("Property");

            propElem.SetAttribute("Name", propName);

            if (!String.IsNullOrEmpty(valueMimeType))
            {
                propElem.SetAttribute("MimeType", valueMimeType);
            }
            
            propElem.InnerText = value;
            parentElem.AppendChild(propElem);
        }

        private static void LoadRequiredProperties(XmlElement personElem, HeadLinxUser person)
        {
            HeadLinxPropertyMap targetMap = person._propMap;

            string selectedUser = personElem.GetAttribute("SelectedUser");

            if (String.Compare(selectedUser, "true", true) == 0)
            {
                person.SelectedUser = true;
            }

            foreach (string propName in ADUserPropertyNames.All)
            {
                string query = String.Format("Property[@Name='{0}']", propName);
                XmlNode node = personElem.SelectSingleNode(query);
                XmlElement elemProp = node as XmlElement;

                if (elemProp != null)
                {
                    string mimeType = elemProp.GetAttribute("MimeType");
                    if (mimeType == "image/jpeg")
                    {
                        byte[] encodedData = Convert.FromBase64String(elemProp.InnerText);
                        targetMap.AddProperty(propName, encodedData);
                    }
                    else
                    {
                        targetMap.AddProperty(propName, elemProp.InnerText);
                    }
                }
            }
        }

        private static void SaveRequiredProperties(HeadLinxUser person, XmlElement personElem)
        {
            HeadLinxPropertyMap propMap = person._propMap;

            //Debug.WriteLine("Saving props for person: " + person.DisplayName);

            if (person.SelectedUser)
            {
                personElem.SetAttribute("SelectedUser", "true");
            }

            foreach (string propName in ADUserPropertyNames.All)
            {
                if (propMap.ContainsKey(propName))
                {
                    object value = propMap[propName].TryGetFirstValue();

                    //Debug.WriteLine(String.Format("\t{0}={1}", propName, value.ToString()));

                    if (value != null)
                    {
                        if (value is string)
                        {
                            AddChildPropertyElement(personElem, propName, "text/plain", value.ToString());
                        }
                        else if (value is System.Byte[])
                        {
                            AddChildPropertyElement(personElem, propName, "image/jpeg", Convert.ToBase64String((byte[])value));
                        }
                        else
                        {
                            AddChildPropertyElement(personElem, propName, null, value.ToString());
                        }
                    }
                }
            }
        }

        private static void SaveXmlProps(XmlDocument doc, XmlElement personElem, HeadLinxUser person)
        {
            personElem.SetAttribute("DistinguishedName", person.DistinguishedName);

            SaveRequiredProperties(person, personElem);

            foreach (HeadLinxUser childPerson in person.DirectReports.Values)
            {
                XmlElement childElem = doc.CreateElement("Object");
                SaveXmlProps(doc, childElem, childPerson);
                personElem.AppendChild(childElem);
            }
        }

        public static string DumpHierarchy(HeadLinxUser user, int level)
        {
            string ret = DumpSingle(user, level);

            foreach (HeadLinxUser child in user.DirectReports.Values)
            {
                ret += DumpHierarchy(child, level + 1);
            }

            return ret;
        }

        public HeadLinxUserDictionary DirectReports
        {
            get
            {
                return _directReports;
            }
        }

        public bool SelectedUser
        {
            get
            {
                return _selectedUser;
            }
            set
            {
                _selectedUser = value;
            }
        }

        public string DistinguishedName
        {
            get
            {
                return _propMap.TryGetPropertyAsString(ADUserPropertyNames.DISTINGUISHED_NAME);
            }
        }

        public string FullName
        {
            get
            {
                string middleName = this.MiddleName;

                if (String.IsNullOrEmpty(middleName))
                {
                    return String.Format("{0} {1}", this.FirstName, this.LastName);
                }
                else
                {
                    return String.Format("{0} {1} {2}", this.FirstName, middleName, this.LastName);
                }
            }
        }

        public string ManagerDistinguishedName
        {
            get
            {
                return _propMap.TryGetPropertyAsString(ADUserPropertyNames.MANAGER);
            }
        }

        public string DisplayName
        {
            get
            {
                return _propMap.TryGetPropertyAsString(ADUserPropertyNames.DISPLAY_NAME);
            }
        }

        public string FirstName
        {
            get
            {
                return _propMap.TryGetPropertyAsString(ADUserPropertyNames.FIRST_NAME);
            }
        }

        public string MiddleName
        {
            get
            {
                return _propMap.TryGetPropertyAsString(ADUserPropertyNames.MIDDLE_NAME);
            }
        }

        public string LastName
        {
            get
            {
                return _propMap.TryGetPropertyAsString(ADUserPropertyNames.LAST_NAME);
            }
        }

        public string EmailAlias
        {
            get
            {
                return _propMap.TryGetPropertyAsString(ADUserPropertyNames.EMAIL_ALIAS);
            }
        }

        public string Title
        {
            get
            {
                return _propMap.TryGetPropertyAsString(ADUserPropertyNames.TITLE);
            }
        }

        public string Company
        {
            get
            {
                return _propMap.TryGetPropertyAsString(ADUserPropertyNames.COMPANY);
            }
        }

        public string Department
        {
            get
            {
                return _propMap.TryGetPropertyAsString(ADUserPropertyNames.DEPARTMENT);
            }
        }

        public string CostCenter
        {
            get
            {
                return _propMap.TryGetPropertyAsString(ADUserPropertyNames.COST_CENTER);
            }
        }

        public string PhysicalOffice
        {
            get
            {
                return _propMap.TryGetPropertyAsString(ADUserPropertyNames.PHYSICAL_OFFICE);
            }
        }

        public string Phone
        {
            get
            {
                return _propMap.TryGetPropertyAsString(ADUserPropertyNames.PHONE);
            }
        }

        public string MobilePhone
        {
            get
            {
                return _propMap.TryGetPropertyAsString(ADUserPropertyNames.MOBILE_PHONE);
            }
        }

        public string CommonName
        {
            get
            {
                return _propMap.TryGetPropertyAsString(ADUserPropertyNames.COMMON_NAME);
            }
        }

        public string EmailAddress
        {
            get
            {
                return _propMap.TryGetPropertyAsString(ADUserPropertyNames.EMAIL_ADDRESS);
            }
        }

        public string HomePage
        {
            get
            {
                return _propMap.TryGetPropertyAsString(ADUserPropertyNames.WWW_HOME_PAGE);
            }
        }

        public byte[] ThumbnailPhotoData
        {
            get
            {
                return _propMap.TryGetByteArrayProperty(ADUserPropertyNames.THUMBNAIL_PHOTO);
            }
        }

        public string ThumbnailPhotoPath { get; set; }
    }

    public class HeadLinxUserDictionary : Dictionary<string, HeadLinxUser>
    {
        public bool SetIfNotExist(SearchResult result, out HeadLinxUser user)
        {
            string userDN = HeadLinxUtils.TryGetProperty(result, ADUserPropertyNames.DISTINGUISHED_NAME);

            if (!this.TryGetValue(userDN, out user))
            {
                user = HeadLinxUser.CreateFromADSearchResult(result);
                this[userDN] = user;
                return true;
            }

            return false;
        }

        public bool SetIfNotExist(HeadLinxUser user)
        {
            if (!this.ContainsKey(user.DistinguishedName))
            {
                this[user.DistinguishedName] = user;
                return true;
            }

            return false;
        }
    }
}
